/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.aliyun.oss.internal;


import com.ala4.oxcafe.properties.OssProperties;
import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.common.auth.HmacSHA1Signature;
import com.aliyun.oss.common.utils.HttpUtil;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import static com.aliyun.oss.internal.OSSConstants.DEFAULT_CHARSET_NAME;

@Slf4j
public class SignUtils {

    private SignUtils() {
    }

    public static String composeRequestAuthorization(String accessKeyId, String signature) {
        return "OSS " + accessKeyId + ":" + signature;
    }

    public static String buildCanonicalString(String method, String resourcePath, Map<String, String> headers) {

        StringBuilder canonicalString = new StringBuilder();
        canonicalString.append(method).append(OSSConstants.NEW_LINE);

        TreeMap<String, String> headersToSign = new TreeMap<String, String>();

        if (headers != null) {
            for (Entry<String, String> header : headers.entrySet()) {
                if (header.getKey() == null) {
                    continue;
                }

                String lowerKey = header.getKey().toLowerCase();
                if (lowerKey.equals(OSSConstants.CONTENT_TYPE.toLowerCase())
                        || lowerKey.equals(OSSConstants.CONTENT_MD5.toLowerCase())
                        || lowerKey.equals(OSSConstants.DATE.toLowerCase())
                        || lowerKey.startsWith(OSSConstants.OSS_PREFIX)) {
                    headersToSign.put(lowerKey, header.getValue().trim());
                }
            }
        }

        if (!headersToSign.containsKey(OSSConstants.CONTENT_TYPE.toLowerCase())) {
            headersToSign.put(OSSConstants.CONTENT_TYPE.toLowerCase(), "");
        }
        if (!headersToSign.containsKey(OSSConstants.CONTENT_MD5.toLowerCase())) {
            headersToSign.put(OSSConstants.CONTENT_MD5.toLowerCase(), "");
        }

        // Append all headers to sign to canonical string
        for (Entry<String, String> entry : headersToSign.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (key.startsWith(OSSConstants.OSS_PREFIX)) {
                canonicalString.append(key).append(':').append(value);
            } else {
                canonicalString.append(value);
            }

            canonicalString.append(OSSConstants.NEW_LINE);
        }

        // Append canonical resource to canonical string
        canonicalString.append(buildCanonicalizedResource(resourcePath));

        return canonicalString.toString();
    }

    public static String buildSignedURL(GeneratePresignedUrlRequest request, OssProperties ossProperties) {
        String bucketName = request.getBucketName();
        String accessId = ossProperties.getAccessKeyId();
        String accessKey = ossProperties.getAccessKeySecret();
        HttpMethod method = request.getMethod() != null ? request.getMethod() : HttpMethod.GET;

        String expires = String.valueOf(request.getExpiration().getTime() / 1000L);
        String key = request.getKey();
        String resourcePath = OSSUtils.determineResourcePath(bucketName, key);

        HashMap<String, String> requestMessage = new HashMap<String, String>();
        requestMessage.put(OSSConstants.DATE, expires);

        String canonicalResource = "/" + ((bucketName != null) ? bucketName : "") + ((key != null ? "/" + key : ""));
        String canonicalString = buildCanonicalString(method.toString(), canonicalResource, requestMessage);
        String signature = new HmacSHA1Signature().computeSignature(accessKey, canonicalString);

        Map<String, String> params = new LinkedHashMap<String, String>();
        params.put(OSSConstants.EXPIRES, expires);
        params.put(OSSConstants.OSS_ACCESS_KEY_ID, accessId);
        params.put(OSSConstants.SIGNATURE, signature);

        String queryString = HttpUtil.paramToQueryString(params, DEFAULT_CHARSET_NAME);

        return "https://" + ossProperties.getBucket() + "." + ossProperties.getEndpoint() + "/" + resourcePath + "?" + queryString;
    }

    public static String buildCanonicalizedResource(String resourcePath) {
        StringBuilder builder = new StringBuilder();
        builder.append(resourcePath);
        return builder.toString();
    }
}
